
import htm from "./libs/htm.js";
import { h, render, useState } from "./libs/fre.js"

const html = htm.bind(h);
const isObj = (obj) => toString.call(obj) === "[object Object]";
const jdoo = {
    types: {},
    utils: {
        extend() {
            var options, name, src, copy, copyIsArray, clone,
                target = arguments[0] || {},
                i = 1,
                length = arguments.length,
                deep = false;
            if (typeof target === "boolean") {
                deep = target;
                target = arguments[i] || {};
                i++;
            }
            if (typeof target !== "object" && !isFunction(target)) {
                target = {};
            }
            if (i === length) {
                target = this;
                i--;
            }
            for (; i < length; i++) {
                if ((options = arguments[i]) != null) {
                    for (name in options) {
                        copy = options[name];
                        if (name === "__proto__" || target === copy) {
                            continue;
                        }
                        if (deep && copy && (!isObj(copy) || (copyIsArray = Array.isArray(copy)))) {
                            src = target[name];
                            if (copyIsArray && !Array.isArray(src)) {
                                clone = [];
                            } else if (!copyIsArray && !isObj(src)) {
                                clone = {};
                            } else {
                                clone = src;
                            }
                            copyIsArray = false;
                            target[name] = this.extend(deep, clone, copy);
                        } else if (copy !== undefined) {
                            target[name] = copy;
                        }
                    }
                }
            }
            return target;
        },
        apply() {
            let options;
            let name;
            let src;
            let copy;
            let copyIsArray;
            let clone;
            let target = arguments[0] || {};
            let i = 1;
            const length = arguments.length;
            let deep = false;
            if (typeof target === 'boolean') {
                deep = target;
                target = arguments[1] || {};
                i = 2;
            }
            if (typeof target !== 'object' && typeof target !== 'function') {
                target = {};
            }
            if (length === i) {
                target = this;
                --i;
            }
            for (; i < length; i++) {
                if ((options = arguments[i]) != null) {
                    for (name in options) {
                        if (name && name[0] === '$') {
                            continue;
                        }
                        src = target[name];
                        copy = options[name];
                        if (target === copy) {
                            continue;
                        }
                        if (deep && copy && (isObj(copy) || (copyIsArray = Array.isArray(copy)))) {
                            if (copyIsArray) {
                                copyIsArray = false;
                                clone = src && Array.isArray(src) ? this.extend(deep, [], src) : [];
                            } else {
                                clone = src && isObj(src) ? this.extend(deep, {}, src) : {};
                            }
                            target[name] = this.extend(deep, clone, copy);
                        } else if (copy !== undefined) {
                            if (copy instanceof Function) {
                                jdoo.utils.defineMethod(target, name, copy);
                            } else {
                                target[name] = copy;
                            }
                        }
                    }
                }
            }
            return target;
        },
        defineMethod: function (owner, name, body) {
            const clone = function (method) {
                let newMethod, prop;
                newMethod = function () {
                    return method.apply(this, arguments);
                };
                for (prop in method) {
                    if (method.hasOwnProperty(prop)) {
                        newMethod[prop] = method[prop];
                    }
                }
                return newMethod;
            };
            if (body.$owner) {
                const origin = body;
                body = clone(body);
                body.$origin = origin;
            }
            owner[name] = body;
            body.$name = name;
            body.$owner = owner.$class;
        },
    },
    define(name, define) {
        const clz = function () {
            if (this && this.new) {
                this.new(...arguments);
            } else {
                return new clz().new(...arguments);
            }
        };
        const prop = clz.prototype;
        const addBase = function (b) {
            const base = jdoo.types[b];
            if (base) {
                clz.$bases.push(base);
                jdoo.utils.apply(true, prop, base.prototype);
                jdoo.utils.apply(true, clz.$statics, base.$statics);
            } else {
                throw new Error(name + ' 不能扩展未定义的 ' + b);
            }
        };
        prop.$class = clz;
        clz.$statics = {};
        if (typeof define === 'function') {
            define = define();
        }
        define = define || {};
        jdoo.utils.defineMethod(prop, 'new', function () {
        });
        clz.$bases = [];
        if (typeof define.extends === 'string') {
            addBase(define.extends);
        } else if (Array.isArray(define.extends)) {
            for (let i = define.extends.length - 1; i >= 0; i--) {
                const b = define.extends[i];
                if (typeof b === 'string') {
                    addBase(b);
                }
            }
        }
        if (define.statics) {
            jdoo.utils.apply(true, clz.$statics, define.statics);
            delete define.statics;
        }
        jdoo.utils.apply(true, clz, clz.$statics);
        jdoo.utils.apply(true, prop, define);
        prop.callSuper = function () {
            if (!this.$super) {
                this.$super = {};
            }
            const stack = new Error().stack.split('\n')[2].trim().split(/\s+/)[1].trim().split('.');
            const name = stack[stack.length - 1];
            const bases = this.$super[name]?.$bases || this.$class.$bases;
            for (let i = bases.length - 1; i >= 0; i--) {
                let baseMethod = bases[i].prototype[name];
                if (baseMethod) {
                    const sp = this.$super[name];
                    this.$super[name] = bases[i];
                    if (baseMethod.$origin) {
                        this.$super[name] = baseMethod.$origin.$owner;
                        baseMethod = baseMethod.$origin;
                    }
                    const result = baseMethod.apply(this, arguments);
                    this.$super[name] = sp;
                    return result;
                }
            }
        };
        clz.$name = name;
        jdoo.types[name] = clz;
        const parts = name.split('.');
        let part = window;
        for (let i = 0; i < parts.length - 1; i++) {
            if (!window[parts[i]]) {
                window[parts[i]] = {}
            }
            part = window[parts[i]];
        }
        part[parts[parts.length - 1]] = clz;
        return clz;
    }
};

jdoo.define("jdoo.Component", {
    new(props) {
        if(isObj(props)){
            jdoo.utils.extend(this, props);
        }
        return html`<${this._render.bind(this)} />`;
    },
    init() { },
    _render() {
        let [state, _] = useState({});
        if (!state.inited) {
            this.init();
            state.inited = true;
            state.target = this;
        }
        return state.target.render();
    },
    render() {
        return html``;
    },
    useState(data) {
        let me = this;
        let [state, setState] = useState(true);
        Object.keys(data).forEach(key => {
            let internalValue = data[key];
            Object.defineProperty(data, key, {
                get() {
                    return internalValue
                },
                set(newVal) {
                    internalValue = newVal;
                    state = !state;
                    setState(state);
                },
            })
        })
        return data;
    },
    mount(dom) {
        render(html`<${this._render.bind(this)} />`, dom);
    },
});

jdoo.component = function (name, define) {
    if (typeof define === 'function') {
        define = define();
    }
    define.extends = define.extends || 'jdoo.Component';
    define.xtype = define.xtype || name;
    return jdoo.define(name, define);
}

export { jdoo, html };